﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
using JtextEpicsInterface;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace JeSequencer
{
    /// <summary>
    /// <para>this is the core of the sequencer</para>
    /// it incharges of the state change
    /// <para>note that all members in the class is public course it need to be xml serialized</para>
    /// </summary>
    [XmlInclude(typeof(State))]
    [XmlInclude(typeof(StateS))]
    [XmlInclude(typeof(Condition))]
    [Serializable]
    public class StateMachine
    {

        public const string LIVETIME = "LIVETIME";
        /// <summary>
        /// real current state
        /// </summary>
        private int cs; //real current state
        /// <summary>
        /// real next state
        /// </summary>
        private int ns;
        private object inputLock;   //when input changes they are not allowed to change ns at same time
        public  StateS MyStates;
        private int LastState;
        private int LiveTime;    //put this in input list "LIVETIME"
        public int DefSt;   //the default state if in undef state or start this state is loaded

        public string Desc; //description
        /// <summary>
        /// its key is the pv name,value is the pv value,the out side world can change this and then call inputchange
        /// </summary>
        [XmlIgnore]
        public Hashtable InputList;
        
        //for hashtable cannot be serialized
        public ArrayList InputlistKey;
        public ArrayList InputlistValue;   //initial value

        public void DoBeforeSerialize()
        {
            InputlistKey.Clear();
            InputlistValue.Clear();
            
            foreach (DictionaryEntry inputs in InputList)
            {
                try
                {
                    InputlistKey.Add(inputs.Key.ToString());
                    InputlistValue.Add(inputs.Value.ToString());
                }
                catch { }
            }
            //hash to array me last
        }
        public void RecoverFromSerialze()
        {
            inputLock = new object();
            InputList = new Hashtable();
            for (int i = 0; i < InputlistKey.Count; i++)
            {
                try
                {
                    InputList.Add(InputlistKey[i], InputlistValue[i]);
                }
                catch
                {
                    //may be live time is added
                }
            }
            MyStates.Recover();
        }

        public StateMachine()
        {
            inputLock = new object();
            InputList = new Hashtable();
            InputList.Add(LIVETIME, 0);
            MyStates = new StateS();
            //add live time to inputlist
            InputlistKey = new ArrayList();
            InputlistValue = new ArrayList(); 
        }

        /// <summary>
        /// Starts this state machine.
        /// <para>add monitor,and populate the input list</para>
        /// </summary>
        public void Start()
        {
            //do this at last
            cs = DefSt;
            ns = cs;
            MyStates.SetInput(InputList);
        }




        /// <summary>
        /// Updates the state.
        /// simplly cs<=ns
        /// </summary>
        public void UpdateState()
        {
            LastState = cs;
            cs = ns;
            DoChangeAction(LastState,cs);
            //this.inputChanged(); //cs is also a input
            if (cs == LastState)
            {
                LiveTime += 1;
            }
            else
            {
                LiveTime = 0;
                this.inputChanged(); //cs is also a input
            }
            InputList[LIVETIME] = LiveTime;
            
        }

        /// <summary>
        /// find which action should do and Does the action.
        /// </summary>
        /// <param name="inState">State of the in.</param>
        /// <param name="outState">State of the out.</param>
        private void DoChangeAction(int inState, int outState)
        {
            foreach (DictionaryEntry tCon in this.MyStates[inState].Conditions)
            {
                if (((Condition)(tCon.Value)).NextState == outState)
                {
                    ((Condition)(tCon.Value)).DoChangeAction();
                }
            }
        }

        /// <summary>
        /// when Inputs changes,you need to calculate new next state.
        /// </summary>
        public void inputChanged()
        {
            lock (inputLock)
            {
                if (MyStates[cs] != null)
                {
                    ns = MyStates[cs].CheckConditions();
                }
                else
                {
                    ns = DefSt;
                }
            }
        }

        public void SaveToFile(string filePath)
        {
            this.DoBeforeSerialize();
            XmlSerializer Xs=new XmlSerializer(typeof(StateMachine));
            Stream Fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
            Xs.Serialize(Fs, this);
            Fs.Close();

        }

        public static StateMachine OpenFromFile(string filePath)
        {
            
            XmlSerializer Xs = new XmlSerializer(typeof(StateMachine));
            Stream Fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            StateMachine Stm= (StateMachine)Xs.Deserialize(Fs);
            Stm.RecoverFromSerialze();
            Fs.Close();
            return Stm;
        }

        #region property
        public int CurrentState
        {
            get
            {
                return cs;
            }
            set
            {
                cs = value;
            }

        }
        #endregion

    }





    /// <summary>
    /// state in the state machine will determine the next state
    /// </summary>
    [Serializable]
    public class State
    {
        public int StateNo;
        public string StateName;
        [XmlIgnore]
        public Hashtable InputList;
        
        public int DefaultNs;
        /// <summary>
        /// the key is the next state 
        /// </summary>
        [XmlIgnore]
        public Hashtable Conditions;
        public ArrayList Condi_S;  //for serialize,add in add condition
        //public int TimeOut;
        public State(int stateNo,int defNs,string Name)
        {
            Conditions = new Hashtable();
            StateNo = stateNo;
            DefaultNs = defNs;
            StateName=Name;
            Condi_S = new ArrayList();
        }

        public State()
        {
            Conditions = new Hashtable();
            Condi_S = new ArrayList();
        }
        /// <summary>
        /// Checks the conditions. to see what is the next state
        /// </summary>
        /// <returns></returns>
        public int CheckConditions()
        {
            foreach (DictionaryEntry tCon in Conditions)
            {
                if (((Condition)(tCon.Value)).CheckMe(InputList) == true)
                {
                    //((Condition)(tCon.Value)).DoChangeAction();
                    return ((Condition)(tCon.Value)).NextState;
                }
            }

            return DefaultNs; 
        }
        public void AddCondition(string expr,int ns,string myName)
        {
            Condition tCon = new Condition(expr, ns,myName);
            Conditions.Add(myName, tCon);
            Condi_S.Add(tCon);            
        }
        public void RemoveConditon(string conName)
        {
            Conditions.Remove(conName);
            for (int i = 0; i < Condi_S.Count; i++)
            {
                if (((Condition)Condi_S[i]).Name == conName)
                {
                    Condi_S.RemoveAt(i);
                    return;
                }
            }
        }

        /// <summary>
        /// Recovers from serialize.you dont really have to do this
        /// </summary>
        public void Recover()
        {
            foreach (object tCon in Condi_S)
            {
                Conditions.Add(((Condition)tCon).Name, (Condition)tCon);
                ((Condition)tCon).Recover();
            }
        }


    }

    /// <summary>
    /// contains a expression,when check condition you provide a inputlist,then it conbine with the expression caculate true or false ,then return 
    /// </summary>
    [Serializable]
    public class Condition
    {
        [XmlIgnore]
        private ScriptRuntime pyRun;
        [XmlIgnore]
        private ScriptEngine pyEng; 
        public delegate void ChangeStateHandler();

        /// <summary>
        /// just give each con a name,to locate them
        /// </summary>
        public string Name;
        /// <summary>
        /// Occurs when [on state change].
        /// When in a certain state ,and changes to another state you can do a action by add to this event.
        /// </summary>
        public event ChangeStateHandler OnStateChange;
        /// <summary>
        /// use python exprssions,
        /// before eval the expression ,it replace the strings in the expression with input in the inputlist 
        /// </summary>
        public string ConditonExpr;
        public int NextState;
        public  Condition(string expr,int ns,string myName)
        {
            pyRun = Python.CreateRuntime();
             pyEng = pyRun.GetEngine("python");
            ConditonExpr=expr;
            NextState=ns;
            Name = myName;
           
        }

        public Condition()
        {
            pyRun = Python.CreateRuntime();
            pyEng = pyRun.GetEngine("python");
        }
        /// <summary>
        /// Checks me.Replace expression with input ,use python expression
        /// </summary>
        /// <param name="InputList">The input list.</param>
        /// <returns></returns>
        public bool CheckMe(Hashtable InputList)
        {
            //make thte puthon expression
            string expr=ConditonExpr;
            foreach (DictionaryEntry inputname in InputList)
            {
                
                expr = Regex.Replace(expr, inputname.Key.ToString(), inputname.Value.ToString());
            }
            //evaluate the expr
            if (pyEng.Execute<bool>(expr) == true)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// when conditon is true the change state and do Does the change action.
        /// </summary>
        public void DoChangeAction()
        {
            if (OnStateChange != null)
            {
                OnStateChange();
            }
        }

        /// <summary>
        /// Recovers this instance from serialize.you dont nedd to do this
        /// </summary>
        public void Recover()
        {
            pyRun = Python.CreateRuntime();
            pyEng = pyRun.GetEngine("python");
        }

    }

    /// <summary>
    /// collection of state
    /// </summary>
    [Serializable]
    public class StateS
    {
        [XmlIgnore]
        private Hashtable myStates;

        public  ArrayList States_S;   //for serialize
        /// <summary>
        /// Initializes a new instance of the <see cref="StateS"/> class.
        /// </summary>
        public StateS()
        {
            myStates = new Hashtable();
            States_S = new ArrayList();
        }

        /// <summary>
        /// Sets the input list of each state.
        /// </summary>
        /// <param name="Inputlist">The inputlist.</param>
        public void SetInput(Hashtable Inputlist)
        {
            foreach (DictionaryEntry tState in myStates)
            {
                ((State)(tState.Value)).InputList = Inputlist;
            }
        }

        public void addState(State St)
        {
            myStates.Add(St.StateNo, St);
            States_S.Add(St);
        }

        public void addState(int stateNo, int defNs, string Name)
        {
            State st = new State(stateNo, defNs, Name);
            this.addState(st);
        }
        public void Recover()
        {
            foreach (object st in States_S)
            {
                myStates.Add(((State)st).StateNo,(State)st);
                ((State)st).Recover();
            }
        }


        /// <summary>
        /// Removes the state.
        /// </summary>
        /// <param name="stateNo">The state no.</param>
        public void RemoveState(int stateNo)
        {
            myStates.Remove(stateNo);
            for (int i = 0; i < States_S.Count;i++ )
            {
                if (((State)States_S[i]).StateNo == stateNo)
                {
                    States_S.RemoveAt(i);
                    return;
                }
            }
        }

        public State this[int StateNo]
        {
            get 
            {
                if (myStates.Contains(StateNo))
                {
                    return (State)myStates[StateNo];
                }
                else
                {
                    return null;
                }
            }
        }

        /// <summary>
        /// check if Contains the state with given state Number.
        /// </summary>
        /// <param name="stateNo">The state no.</param>
        /// <returns></returns>
        public Boolean containState(int stateNo)
        {
            return myStates.ContainsKey(stateNo);
        }

    }

}
